/*
 * Socket.cpp
 *
 *  Created on: Jun 22, 2016
 *      Author: Lucifer
 */

#include "./Socket.h"

#if PLATFORM == PLATFORM_WINDOWS
#	include <Ws2tcpip.h>
#	define inet_pton	InetPtonA
#else
#	include <arpa/inet.h>
#	include <sys/socket.h>
#	include <sys/ioctl.h>
#	if PLATFORM == PLATFORM_APPLE
#		include <netinet/tcp.h>
#	else
#		include <unistd.h>
#	endif
#endif
#include <string.h>
#include <boost/thread.hpp>

namespace BFX {

static void Socket_initializeEnv() {
#if PLATFORM == PLATFORM_WINDOWS
	struct Initializer {
		void operator()() {
			WORD wVerReq = MAKEWORD(2, 2);
			WSADATA wsaData;
			int retval = WSAStartup(wVerReq, &wsaData);
			BFX_ASSERT(retval == 0);
			SocketError::set((retval == 0) ? SocketError::EC_Success : SocketError::EC_IllegalStateError);
		}
	};
	static boost::once_flag __initCtx;
	boost::call_once(__initCtx, Initializer());
#endif
}

InetAddress::InetAddress(const sockaddr* pAddr) {
	if (pAddr == nullptr || pAddr->sa_family == AF_UNSPEC) {
		_addr.sin_family = AF_Unspecified;
	} else {
		int addrLen = (pAddr->sa_family == AF_INET) ?
						sizeof(sockaddr_in) :
						((pAddr->sa_family == AF_INET6) ? sizeof(sockaddr_in6) : -1);
		BFX_ASSERT(addrLen > 0 && "Unsupported address family");
		// Ensures socket environment initialized.
		Socket_initializeEnv();
		memcpy(&_addr, pAddr, addrLen);
	}
}

InetAddress::InetAddress(uint16_t port, bool loopback, AddressFamily addressFamily) {
	BFX_ASSERT(port >= 0);
	BFX_ASSERT(addressFamily ==  AF_IPv4 || addressFamily == AF_IPv6);

	// Ensures socket environment initialized.
	Socket_initializeEnv();

	if (addressFamily == AF_IPv6) {
		memset(&_addr6, 0, sizeof(sockaddr_in6));
		_addr6.sin6_family = AF_INET6;
		_addr6.sin6_addr = (loopback ? in6addr_loopback : in6addr_any);
		_addr6.sin6_port = htons(port);
	} else {
		memset(&_addr, 0, sizeof(sockaddr_in));
		_addr.sin_family = AF_INET;
		_addr.sin_addr.s_addr = htonl(loopback ? INADDR_LOOPBACK : INADDR_ANY);
		_addr.sin_port = htons(port);
	}
}
InetAddress::InetAddress(const char* address, uint16_t port, AddressFamily addressFamily) {
	BFX_ASSERT(address && *address);
	BFX_ASSERT(port > 0);
	BFX_ASSERT(addressFamily ==  AF_IPv4 || addressFamily == AF_IPv6);

	// Ensures socket environment initialized.
	Socket_initializeEnv();

	int retval = -1;
	if (addressFamily == AF_IPv6) {
		memset(&_addr6, 0, sizeof(sockaddr_in6));
		_addr6.sin6_family = AF_INET6;
		_addr6.sin6_port = htons(port);
		retval = inet_pton(AF_INET6, address, &_addr6.sin6_addr);
		BFX_ASSERT(retval == 1);
	} else {
		memset(&_addr, 0, sizeof(sockaddr_in));
		_addr.sin_family = AF_INET;
		_addr.sin_port = htons(port);
		retval = inet_pton(AF_INET, address, &_addr.sin_addr);
		BFX_ASSERT(retval == 1);
	}

	// 1 on success.
	if (retval != 1) {
		// 0 if the given address is not a valid IPv4 address string.
		// -1 unsupported address family.
		_addr.sin_family = AF_UNSPEC;
		SocketError::set(
				(retval == 0) ?
						SocketError::EC_InvalidArgument :
						SocketError::EC_AddressFamilyNotSupported);
	}
}

Socket::Socket() :
		_fd(INVALID_FILDES) {
}

Socket::Socket(InetAddress* addr) :
		_fd(INVALID_FILDES) {
	BFX_ASSERT(addr);

	bool retval = bind(addr);
	BFX_ASSERT(retval);
	if (!retval) {
		close();
	}
}

Socket::~Socket() {
	close();
}

bool Socket::initialize(InetAddress::AddressFamily family) {
	BFX_ASSERT((family == InetAddress::AF_IPv4) || (family == InetAddress::AF_IPv6));
	BFX_ASSERT(!isValid());

	_fd = ::socket(family, SOCK_STREAM, IPPROTO_TCP);
	SocketError::update(INVALID_FILDES == _fd);
	return (INVALID_FILDES != _fd);
}

bool Socket::bind(InetAddress* addr) {
	BFX_ASSERT(addr);
	BFX_ASSERT(!isValid());

	if (!initialize(addr->getAddressFamily())) {
		return false;	// unable to create socket.
	}

	int retval = ::bind(_fd, addr->getSockAddr(), addr->getSockAddrSize());
	SocketError::update(-1 == retval);
	return (-1 != retval);
}

bool Socket::listen(int backlog) {
	BFX_ASSERT(_fd != INVALID_FILDES);

	int retval = ::listen(_fd, backlog);
	SocketError::update(-1 == retval);
	return (-1 != retval);
}

bool Socket::connect(InetAddress* addr) {
	BFX_ASSERT(addr);
	BFX_ASSERT(!isValid());

	if (!initialize(addr->getAddressFamily())) {
		return false;	// unable to create socket.
	}
	int retval = ::connect(_fd, addr->getSockAddr(), addr->getSockAddrSize());
	SocketError::update(-1 == retval);
	return (-1 != retval);
}

bool Socket::accept(Socket* rSocket, InetAddress* rAddr) {
	BFX_ASSERT(rSocket);
	BFX_ASSERT(!isValid());

	sockaddr* addr = nullptr;
	socklen_t addrLen = 0;
	if (rAddr) {
		addr = const_cast<sockaddr*>(rAddr->getSockAddr());
		addrLen = (socklen_t) rAddr->getSockAddrSize();
	}
	fildes_t fd = ::accept(_fd, addr, addrLen ? &addrLen : 0);
	BFX_ASSERT(rAddr == nullptr || addrLen == rAddr->getSockAddrSize());
	if (fd != INVALID_FILDES) {
		return rSocket->attach(fd);
	}
	SocketError::updateSysError();
	return false;
}


bool Socket::attach(fildes_t fd) {
	BFX_ASSERT(fd != INVALID_FILDES);
	BFX_ASSERT(!isValid());

	if (fd == INVALID_FILDES) {
		SocketError::set(SocketError::EC_BadFileDescriptor);
		return false;
	} else if (_fd != INVALID_FILDES) {
		// XXX Require an illegal state error code
		SocketError::set(SocketError::EC_IllegalStateError);
		return false;
	}

	_fd = fd;
	// XXX Should we clear the last error?
	return true;
}

int Socket::send(const void* buf, int bufLen, int flags) {
	BFX_ASSERT(_fd != INVALID_FILDES);
	if (bufLen == 0)
		return 0;
	BFX_ASSERT(buf);
	int retval = ::send(_fd, (const char*) buf, bufLen, flags);
	SocketError::update(-1 == retval);
	return retval;
}

int Socket::receive(void* buf, int bufLen, int flags) {
	BFX_ASSERT(_fd != INVALID_FILDES);
	if (bufLen == 0)
		return 0;
	BFX_ASSERT(buf);
	int retval = ::recv(_fd, (char*) buf, bufLen, flags);
	SocketError::update(-1 == retval);
	return retval;
}

void Socket::close() {
	if (_fd != INVALID_FILDES) {
#if PLATFORM == PLATFORM_WINDOWS
		::closesocket(_fd);
#else
		::close(_fd);
#endif
		_fd = INVALID_FILDES;
	}
}

bool Socket::shutdown(ShutdownType how) {
	BFX_ASSERT(_fd != INVALID_FILDES);

	int retval = ::shutdown(_fd, how);
	SocketError::update(-1 == retval);
	return (-1 != retval);
}

bool Socket::setSockOpt(int optionName, const void* optionValue, int optionLen, int level) {
	BFX_ASSERT(_fd != INVALID_FILDES);
	int retval = ::setsockopt(_fd, level, optionName, (const char*) optionValue, optionLen);
	SocketError::update(-1 == retval);
	return (-1 != retval);
}

bool Socket::getSockOpt(int optionName, void* optionValue, int* optionLen, int level) {
	BFX_ASSERT(_fd != INVALID_FILDES);
	BFX_ASSERT(sizeof(socklen_t) == sizeof(int));
	int retval = ::getsockopt(_fd, level, optionName, (char*) optionValue, (socklen_t*) optionLen);
	SocketError::update(-1 == retval);
	return (-1 != retval);
}

bool Socket::getSockName(InetAddress* rAddr) {
	BFX_ASSERT(rAddr);
	BFX_ASSERT(_fd != INVALID_FILDES);

	sockaddr* addr = const_cast<sockaddr*>(rAddr->getSockAddr());
	socklen_t rAddrLen = BFX_MAX(sizeof(sockaddr_in6), sizeof(sockaddr_in6));
	int retval = ::getsockname(_fd, addr, &rAddrLen);
	SocketError::update(-1 == retval);
	return (-1 != retval);
}

bool Socket::ioCtl(long command, void* arg) {
#if PLATFORM == PLATFORM_WINDOWS
	int retval = ::ioctlsocket(_fd, command, (u_long*) arg);
#else
	int retval = ::ioctl(_fd, command, arg);
#endif
	SocketError::update(-1 == retval);
	return (-1 != retval);
}

bool Socket::setNonBlocking(bool state) {
	unsigned long optval = state ? 1 : 0;
	return ioCtl(FIONBIO, &optval);
}

bool Socket::setReuseAddress(bool state) {
	BFX_ASSERT(_fd != INVALID_FILDES);
	int optval = state ? 1 : 0;
	return setSockOpt(SO_REUSEADDR, &optval, sizeof(optval), SOL_SOCKET);
}

bool Socket::setReusePort(bool state) {
	BFX_ASSERT(_fd != INVALID_FILDES);
#ifdef SO_REUSEPORT
	int optval = state ? 1 : 0;
	return setSockOpt(SO_REUSEPORT, &optval, sizeof(optval), SOL_SOCKET);
#else
	// SO_REUSEPORT is not supported.
	return false;
#endif
}

bool Socket::setKeepAlive(bool state) {
	int optval = state ? 1 : 0;
	return setSockOpt(SO_KEEPALIVE, &optval, sizeof(optval), SOL_SOCKET);
}

bool Socket::setTcpNoDelay(bool state) {
	int optval = state ? 1 : 0;
	return setSockOpt(TCP_NODELAY, &optval, sizeof(optval), SOL_SOCKET);
}

} /* namespace BFX */
